#include "StdAfx.h"
#include "TdColumn.h"
#include "TdUtils.h"

using namespace System;
using namespace Teradata::Client::Provider;
namespace TPT = TdParallelTransportApi;

TPT::TdColumn::TdColumn(System::String^ definition)
{
	definition = definition->Trim();
	String^ token;
	int off = 0;

	this->m_ColumnName = TPT::GetToken(definition, &off);

	while (off < definition->Length && Char::IsWhiteSpace(definition[off])) off++;

	if (off >= definition->Length)
	{
		throw gcnew ArgumentException("Invalid Column Definition.", "definition");
	}

	this->m_DataType = (TdType)Enum::Parse(TdType::typeid, TPT::GetToken(definition, &off), true);

	while (off >= 0 && off < definition->Length && Char::IsWhiteSpace(definition[off])) off++;

	if (off >= 0 && off < definition->Length)
	{
		if (definition[off] != '(')
		{
			throw gcnew ArgumentException("Invalid Column Definition.", "definition");
		}

		int endOff = definition->IndexOf(')', off);

		if (endOff < 0)
		{
			throw gcnew ArgumentException("Invalid Column Definition.", "definition");
		}

		array<System::String^>^ parts = definition->Substring(off + 1, endOff - off - 1)->Split(',');
		if (parts->Length == 1)
		{
			this->m_Length = Int32::Parse(parts[0]);

			this->InitializeType(this->m_DataType, this->m_Length);
		}
		else if (parts->Length == 2)
		{
			this->m_Precision = Int32::Parse(parts[0]);
			this->m_Scale = Int32::Parse(parts[1]);

			this->InitializeType(this->m_DataType, this->m_Precision, this->m_Scale);
		}
		else
		{
			throw gcnew ArgumentException("Invalid Column Definition.", "definition");
		}
	}
	else
	{
		this->InitializeType(this->m_DataType);
	}
}

TPT::TdColumn::TdColumn(String^ columnName, TdType dataType) :
	m_ColumnName(columnName), m_DataType(dataType), m_Length(0), m_Precision(0), m_Scale(0)
{
	this->InitializeType(dataType);
}

TPT::TdColumn::TdColumn(String^ columnName, TdType dataType, int length) :
	m_ColumnName(columnName), m_DataType(dataType), m_Length(length), m_Precision(0), m_Scale(0)
{
	this->InitializeType(dataType, length);
}

TPT::TdColumn::TdColumn(String^ columnName, TdType dataType, int precision, int scale) :
	m_ColumnName(columnName), m_DataType(dataType), m_Length(0), m_Precision(precision), m_Scale(scale)
{
	this->InitializeType(dataType, precision, scale);
}

void
TPT::TdColumn::InitializeType(TdType dataType)
{
	switch (dataType)
	{
	case TdType::Byte:
		this->m_Length = 1;
	case TdType::ByteInt:
		this->m_Size = 1;
		break;
	case TdType::Char:
	case TdType::SmallInt:
		this->m_Size = 2;
		break;
	case TdType::Integer:
	case TdType::Decimal:
	case TdType::Date:
		this->m_Size = 4;
		break;
	case TdType::BigInt:
	case TdType::Double:
		this->m_Size = 8;
		break;
	case TdType::VarChar:
	case TdType::VarByte:
		throw gcnew ArgumentException("The Specified Type Requires a Length Specification", "dataType");
	default:
		throw gcnew ArgumentException("The Specified Type Is Not Supported", "dataType");
	}
}

void
TPT::TdColumn::InitializeType(TdType dataType, int length)
{
	switch (dataType)
	{
	case TdType::Char:
	case TdType::VarChar:
		this->m_Size = this->m_Length * 2;
		break;
	case TdType::Byte:
	case TdType::VarByte:
		this->m_Size = this->m_Length;
		break;
	case TdType::ByteInt:
	case TdType::SmallInt:
	case TdType::Integer:
	case TdType::Decimal:
	case TdType::Date:
	case TdType::BigInt:
	case TdType::Double:
		throw gcnew ArgumentException("The Specified Type Does Not Support a Length Specification", "dataType");
	default:
		throw gcnew ArgumentException("The Specified Type Is Not Supported", "dataType");
	}
}

void
TPT::TdColumn::InitializeType(TdType dataType, int precision, int scale)
{
	if (precision < 1)
	{
		throw gcnew ArgumentOutOfRangeException("precision");
	}
	else if (scale > precision)
	{
		throw gcnew ArgumentOutOfRangeException("scale");
	}

	switch (dataType)
	{
	case TdType::Decimal:
		if (precision <= 2)
		{
			this->m_Size = 1;
		}
		else if (precision <= 4)
		{
			this->m_Size = 2;
		}
		else if (precision <= 9)
		{
			this->m_Size = 4;
		}
		else if (precision <= 18)
		{
			this->m_Size = 8;
		}
		else if (precision <= 38)
		{
			this->m_Size = 16;
		}
		else
		{
			throw gcnew ArgumentOutOfRangeException("precision");
		}

		break;
	case TdType::Byte:
	case TdType::ByteInt:
	case TdType::Char:
	case TdType::SmallInt:
	case TdType::Integer:
	case TdType::Date:
	case TdType::BigInt:
	case TdType::Double:
	case TdType::VarChar:
	case TdType::VarByte:
		throw gcnew ArgumentException("The Specified Type Does Not Support Scale and Precision", "dataType");
	default:
		throw gcnew ArgumentException("The Specified Type Is Not Supported", "dataType");
	}
}

String^
TPT::TdColumn::ColumnName::get()
{
	return m_ColumnName;
}

TdType
TPT::TdColumn::DataType::get()
{
	return m_DataType;
}

int
TPT::TdColumn::Length::get()
{
	return m_Length;
}

int
TPT::TdColumn::Precision::get()
{
	return m_Precision;
}

int
TPT::TdColumn::Scale::get()
{
	return m_Scale;
}

int
TPT::TdColumn::Size::get()
{
	return m_Size;
}

TD_DataType
TPT::TdColumn::GetTdDataType(TdType type)
{
	switch (type)
	{
	case TdType::Byte:
		return TD_BYTE;
	case TdType::VarByte:
		return TD_VARBYTE;

	case TdType::ByteInt:
		return TD_BYTEINT;
	case TdType::SmallInt:
		return TD_SMALLINT;
	case TdType::Integer:
		return TD_INTEGER;
	case TdType::BigInt:
		return TD_BIGINT;
	case TdType::Double:
		return TD_FLOAT;
	case TdType::Decimal:
		return TD_DECIMAL;

	case TdType::Char:
		return TD_CHAR;
	case TdType::VarChar:
		return TD_VARCHAR;

	case TdType::Date:
		return TD_DATE;

	case TdType::PeriodDate:
	case TdType::Time:
	case TdType::PeriodTime:
	case TdType::TimeWithZone:
	case TdType::PeriodTimeWithTimeZone:
	case TdType::Timestamp:
	case TdType::PeriodTimestamp:
	case TdType::TimestampWithZone:
	case TdType::PeriodTimestampWithTimeZone:
	case TdType::Graphic:
	case TdType::VarGraphic:
	case TdType::Clob:
	case TdType::Blob:
	default:
		return TD_NONE;
	}
}